﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using HigLabo.Core;

namespace HigLabo.Net.Dropbox
{
    /// <summary>
    /// 
    /// </summary>
    public partial class DropboxClient : OAuth2Client
    {
        public static readonly DropboxClientDefaultSettings Default = new DropboxClientDefaultSettings();
        private HttpProtocolType _protocol = HttpProtocolType.Https;

        /// <summary>
        /// 
        /// </summary>
        public HttpProtocolType Protocol
        {
            get { return _protocol; }
        }
        /// <summary>
        /// 
        /// </summary>
        public RootFolder Root { get; set; }

        public DropboxClient()
            : this("", "", "")
        {
        }
        public DropboxClient(String clientID, String clientSecret, String redirectUrl)
        {
            this.Root = RootFolder.Dropbox;
            this.ClientID = clientID;
            this.ClientSecret = clientSecret;
            this.RedirectUrl = redirectUrl;
            this.AccessToken = new OAuth2AccessToken();
            this.AccessToken.SetProperty(Default.AccessToken);
        }

        public override String GetAuthorizeUrl(OAuth2ResponseType responseType)
        {
            Func<String, String> f = HttpClient.UrlEncode;
            return String.Format("https://www.dropbox.com/1/oauth2/authorize?response_type={0}&client_id={1}&redirect_uri={2}"
                , responseType.ToStringFromEnum().ToLower(), f(this.ClientID), f(this.RedirectUrl));
        }
        protected override HttpRequestCommand CreateGetAccessTokenCommand(String code)
        {
            String url = "https://api.dropbox.com/1/oauth2/token";
            var cm = new HttpRequestCommand(url);
            cm.MethodName = HttpMethodName.Post;
            var d = new Dictionary<String, String>();
            d["grant_type"] = "authorization_code";
            d["code"] = code;
            d["client_id"] = this.ClientID;
            d["client_secret"] = this.ClientSecret;
            d["redirect_uri"] = this.RedirectUrl;
            cm.SetBodyStream(new HttpBodyFormUrlEncodedData(Encoding.UTF8, d));
            return cm;
        }

        private void GetResponseBodyText(String url, DropboxCommand command, Action<String> callback)
        {
            this.GetResponseBodyText(HttpMethodName.Get, url, command, callback);
        }
        private void GetResponseBodyText(HttpMethodName methodName, String url, DropboxCommand command, Action<String> callback)
        {
            this.GetHttpWebResponse(methodName, url, command, res => callback(res.BodyText));
        }
        private void GetHttpWebResponse(HttpMethodName methodName, String url, DropboxCommand command, Action<Byte[]> callback)
        {
            this.GetHttpWebResponse(methodName, url, command, res => callback(res.BodyData));
        }
        private void GetHttpWebResponse(HttpMethodName methodName, String url, DropboxCommand command, Action<HttpResponse> callback)
        {
            String u = _protocol.ToString().ToLower() + "://" + url;
            IDictionary<String, String> d = new Dictionary<String, String>();

            if (methodName == HttpMethodName.Put ||
                methodName == HttpMethodName.Delete)
            { throw new ArgumentException(); }

            if (command != null)
            {
                d = command.GetParameters();
            }
            var cm = this.CreateHttpRequestCommand(methodName, u, d);
            this.GetHttpWebResponse(cm, res => this.GetHttpWebResponseCallback(res, callback));
        }
        private void GetHttpWebResponseCallback(HttpWebResponse response, Action<HttpResponse> callback)
        {
            var res = new HttpResponse(response, this.ResponseEncoding);
            callback(res);
        }
        private void Post(String url, UploadFileCommand command, Action<String> callback)
        {
            var u = _protocol.ToString().ToLower() + "://" + url;
            var d = command.GetParameters();
            d["file"] = command.FileName;
            var cm = this.CreateHttpRequestCommand(HttpMethodName.Post, u, d);
            var boundary = UploadFileCommand.GetBoundaryString();
            cm.ContentType = "multipart/form-data; boundary=\"" + boundary + "\"";
            cm.SetBodyStream(command.CreatePostData(boundary));
            this.GetResponse(cm, res => callback(res.BodyText));
        }
        private HttpRequestCommand CreateHttpRequestCommand(HttpMethodName methodName, String url, IDictionary<String, String> parameters)
        {
            var cm = new HttpRequestCommand(url);
            cm.MethodName = methodName;
            if (this.AccessToken != null)
            {
                cm.Headers[HttpRequestHeader.Authorization] = String.Format("Bearer {0}", this.AccessToken.Value);
            }
            foreach (var item in parameters)
            {
                cm.Headers[item.Key] = item.Value;
            }
            return cm;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="callback"></param>
        /// <param name="command"></param>
        public void GetAccountInfo(GetAccountCommand command, Action<AccountInfo> callback)
        {
            this.GetResponseBodyText(DropboxApiUrl.Version1.GetAccount, command, text => callback(new AccountInfo(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void CreateAccount(CreateAccountCommand command, Action<String> callback)
        {
            this.GetResponseBodyText(DropboxApiUrl.Version1.CreateAccount, command, callback);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void GetMetadata(GetMetadataCommand command, Action<Metadata> callback)
        {
            var url = DropboxApiUrl.GetApiUrl(DropboxApiUrl.Version1.Metadata, this.Root.ToStringFromEnum(), command.Path);
            this.GetResponseBodyText(url, command, text => callback(new Metadata(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="callback"></param>
        public void GetMetadata(String path, Action<Metadata> callback)
        {
            var cm = new GetMetadataCommand();
            cm.Path = path;
            this.GetMetadata(cm, callback);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="callback"></param>
        public void GetFile(String path, Action<Byte[]> callback)
        {
            this.GetFile(new GetFileCommand { Path = path }, callback);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void GetFile(GetFileCommand command, Action<Byte[]> callback)
        {
            var url = DropboxApiUrl.GetApiUrl(DropboxApiUrl.Version1.Files, this.Root.ToStringFromEnum(), command.Path);
            this.GetHttpWebResponse(HttpMethodName.Get, url, command, callback);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void UploadFile(UploadFileCommand command, Action<Metadata> callback)
        {
            if (String.IsNullOrEmpty(command.FolderPath)) { throw new ArgumentException(); }
            string url = DropboxApiUrl.GetApiUrl(DropboxApiUrl.Version1.Files, this.Root.ToStringFromEnum(), command.FolderPath);
            this.Post(url, command, text => callback(new Metadata(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void GetThumbnails(GetThumbnailsCommand command, Action<Byte[]> callback)
        {
            this.GetHttpWebResponse(HttpMethodName.Get
                , DropboxApiUrl.GetApiUrl(DropboxApiUrl.Version1.Thumbnail, this.Root.ToStringFromEnum(), command.Path)
                , command
                , callback);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="size"></param>
        /// <param name="format"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        public void GetThumbnails(String path, ThumbnailSize size, ThumbnailFormat format, Action<Byte[]> callback)
        {
            var cm = new GetThumbnailsCommand();
            cm.Path = path;
            cm.Size = size;
            cm.Format = format;
            this.GetThumbnails(cm, callback);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void CopyFile(CopyFileCommand command, Action<Metadata> callback)
        {
            this.GetResponseBodyText(HttpMethodName.Post, DropboxApiUrl.Version1.CopyFile, command, text => callback(new Metadata(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="fromPath"></param>
        /// <param name="toPath"></param>
        /// <param name="callback"></param>
        public void CopyFile(String fromPath, String toPath, Action<Metadata> callback)
        {
            var cm = new CopyFileCommand();
            cm.FromPath = fromPath;
            cm.ToPath = toPath;
            this.CopyFile(cm, callback);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void MoveFile(MoveFileCommand command, Action<Metadata> callback)
        {
            this.GetResponseBodyText(HttpMethodName.Post, DropboxApiUrl.Version1.MoveFile, command, text => callback(new Metadata(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="fromPath"></param>
        /// <param name="toPath"></param>
        /// <param name="callback"></param>
        public void MoveFile(String fromPath, String toPath, Action<Metadata> callback)
        {
            var cm = new MoveFileCommand();
            cm.FromPath = fromPath;
            cm.ToPath = toPath;
            this.MoveFile(cm, callback);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void CreateFolder(CreateFolderCommand command, Action<Metadata> callback)
        {
            this.GetResponseBodyText(HttpMethodName.Post, DropboxApiUrl.Version1.CreateFolder, command, text => callback(new Metadata(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="callback"></param>
        public void CreateFolder(String path, Action<Metadata> callback)
        {
            var cm = new CreateFolderCommand();
            cm.Path = path;
            this.GetResponseBodyText(HttpMethodName.Post, DropboxApiUrl.Version1.CreateFolder, cm, text => callback(new Metadata(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void Delete(DeleteCommand command, Action<Metadata> callback)
        {
            this.GetResponseBodyText(HttpMethodName.Post, DropboxApiUrl.Version1.Delete, command, text => callback(new Metadata(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="callback"></param>
        public void Delete(String path, Action<Metadata> callback)
        {
            var cm = new DeleteCommand();
            cm.Path = path;
            this.Delete(cm, callback);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void PutFile(PutFileCommand command, Action<Metadata> callback)
        {
            if (String.IsNullOrEmpty(command.Path)) { throw new ArgumentException(); }
            var url = DropboxApiUrl.GetApiUrl(DropboxApiUrl.Version1.FilesPut, this.Root.ToStringFromEnum(), command.Path);
            url = _protocol.ToString().ToLower() + "://" + url;
            var cm = this.CreateHttpRequestCommand(HttpMethodName.Put, url, command.GetParameters());
            cm.SetBodyStream(command.CreatePutData());
            this.GetResponse(cm, res => callback(new Metadata(res.BodyText)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void GetRevisions(GetRevisionsCommand command, Action<List<Metadata>> callback)
        {
            this.GetResponseBodyText(HttpMethodName.Get
                    , DropboxApiUrl.GetApiUrl(DropboxApiUrl.Version1.Revision, this.Root.ToStringFromEnum(), command.Path)
                    , command
                    , text => callback(GetMetadataList(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void Search(SearchCommand command, Action<List<Metadata>> callback)
        {
            this.GetResponseBodyText(HttpMethodName.Post
                    , DropboxApiUrl.GetApiUrl(DropboxApiUrl.Version1.Search, this.Root.ToStringFromEnum(), command.Path)
                    , command
                    , text => callback(GetMetadataList(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="query"></param>
        /// <param name="callback"></param>
        public void Search(String path, String query, Action<List<Metadata>> callback)
        {
            var cm = new SearchCommand();
            cm.Path = path;
            cm.Query = query;
            this.Search(cm, callback);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void Restore(RestoreCommand command, Action<Metadata> callback)
        {
            this.GetResponseBodyText(HttpMethodName.Post
                , DropboxApiUrl.GetApiUrl(DropboxApiUrl.Version1.Restore, this.Root.ToStringFromEnum(), command.Path)
                , command
                , text => callback(new Metadata(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void GetShareableLink(GetShareableLinkCommand command, Action<LinkInfo> callback)
        {
            this.GetResponseBodyText(HttpMethodName.Post
                , DropboxApiUrl.GetApiUrl(DropboxApiUrl.Version1.Share, this.Root.ToStringFromEnum(), command.Path)
                , command
                , text => callback(new LinkInfo(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void GetMediaLink(GetMediaLinkCommand command, Action<LinkInfo> callback)
        {
            this.GetResponseBodyText(HttpMethodName.Post
                , DropboxApiUrl.GetApiUrl(DropboxApiUrl.Version1.Media, this.Root.ToStringFromEnum(), command.Path)
                , command
                , text => callback(new LinkInfo(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void GetCopyRef(GetCopyRefCommand command, Action<CopyRefInfo> callback)
        {
            this.GetResponseBodyText(HttpMethodName.Get
                , DropboxApiUrl.GetApiUrl(DropboxApiUrl.Version1.CopyRef, this.Root.ToStringFromEnum(), command.Path)
                , command
                , text => callback(new CopyRefInfo(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="callback"></param>
        public void GetDelta(GetDeltaCommand command, Action<DeltaInfo> callback)
        {
            this.GetResponseBodyText(HttpMethodName.Post
                , DropboxApiUrl.GetApiUrl(DropboxApiUrl.Version1.Delta, "")
                , command
                , text => callback(new DeltaInfo(text)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="jsontext"></param>
        /// <returns></returns>
        private List<Metadata> GetMetadataList(String jsontext)
        {
            var result = new List<Metadata>();
            var ja = JsonConvert.DeserializeObject(jsontext) as JArray;

            if (ja != null)
            {
                result.AddRange(ja.Select(s => new Metadata(s.ToString())));
            }
            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="callback"></param>
        public void GetRootFolder(Action<Metadata> callback)
        {
            this.GetMetadata("/", callback);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="callback"></param>
        public void GetRootItems(Action<List<Metadata>> callback)
        {
            this.GetMetadata("/", md => callback(md.Contents));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="callback"></param>
        public void GetRootFolders(Action<List<Metadata>> callback)
        {
            this.GetMetadata("/", md =>
            {
                var l = new List<Metadata>();
                foreach (var item in md.Contents)
                {
                    if (item.IsDirectory == true)
                    {
                        l.Add(item);
                    }
                }
                callback(md.Contents);
            });
        }
    }
}
